<?php

class NCore_CharDataPort implements NCore_IPort, NCore_IValue
{
	private $name;
	private $value = NULL;
	private $valid = NULL;
	private $whitelist = array();
	private $encryption = false;
	private $minLength = 1;				// default to require value
	private $maxLength = 10;	
	private $allowNull = false;			// declares whether validation should succeed or fail if the GPCS variable is unset	
	private $allowArray = false;		// declares if the data is expected to be an array
	private $errorMsg = NULL;
    private $errorMsgMinLength = NULL;
    private $errorMsgMaxLength = NULL;
    private $errorMsgFilter = NULL;
    private $errorMsgAllowNull = NULL;
    private $errorMsgWhitelist = NULL;
    private $errorMsgAllowArray = NULL;
	private $filter;
	private $filterType;
	private $valueHasBeenSet = false;
	private $formatter = NULL;
	
	public function __construct($key)
	{
		$this->name = $key;
		$this->filter = new NCore_Port_Filter_Integer(NULL);
        $this->filterType = N::ALLOW_INT;
	}
	public function withName($name)
	{
		if(!preg_match(N::REGEX_KEY, $name))
		{
			throw new Exception('The port name '.$this->getName().' is not valid.  Port names must begin with a letter; contain letters, numbers, or the underscore; and be no longer than 40 characters.');
		}

		$this->name = $name;
		return $this;
	}
	public function getName()
	{
		return $this->name;
	}
     public function getFormattedName()
    {
        return str_replace('_', ' ', ucfirst($this->getName()));
    }
	public function validate()
	{		
		if(!is_null($this->valid))
		{		
			return $this->valid;
		}
		
		$this->initializeValue();
	
		// check for NULLs
		if(is_null($this->value))
		{
			if ($this->allowNull)
            {
                return $this->valid = true;
            }
            else
            {
                if (is_null($this->errorMsg))
                {
                    $this->errorMsg = $this->getErrorMsgAllowNull();
                }

                return $this->valid = false;
            }
		}
		
		// new logic for handling arrays
		if(is_array($this->value))
		{
			if($this->allowArray)
			{
				// if encrypted, decrypt elements first
				// not best for performance, but quick to implement and handy for debugging
				if($this->encryption)
				{
					for($i = 0; $i < count($this->value); $i++)
					{
						$this->value[$i] = N::decrypt($this->value[$i]);
					}
				}
			
				foreach($this->value as $item)
				{
					if(!$this->dataIsValid($item))
					{
						return $this->valid = false;
					}
				}
				
				return $this->valid = true;
			}
			else
			{
                if (is_null($this->errorMsg))
                {
                    $this->errorMsg = $this->getErrorMsgAllowArray();
                }

				return $this->valid = false;
			}
		}
		else
		{
			// check if the value has been encrypted
			if($this->encryption)
			{
				$this->value = N::decrypt($this->value);
			}
			
			return $this->valid = $this->dataIsValid($this->value);
		}			
	}	
	public function dataIsValid($data)
	{	
		if(count($this->whitelist) > 0)
		{
			$valid = in_array($data, $this->whitelist);

            if (!$valid)
            {
                if (is_null($this->errorMsg))
                {
                    $this->errorMsg = $this->getErrorMsgWhitelist();
                }
            }

            return $valid;
		} 
		else
		{
			// if value is '' and minLength is 0, don't wast processing
			if($data == '' && $this->minLength == 0) return true;

			if(strlen($data) > $this->maxLength)
			{
                if (is_null($this->errorMsg))
                {
                    $this->errorMsg = $this->getErrorMsgMaxLength();
                }

				return false;
			}
			if(strlen($data) < $this->minLength)
			{
                if (is_null($this->errorMsg))
                {
                    $this->errorMsg = $this->getErrorMsgMinLength();
                }

				return false;
			}
			if(!$this->filter->filter($data))
			{
                if (is_null($this->errorMsg))
                {
                    $this->errorMsg = $this->getErrorMsgFilter();
                }

				return false;
			}
			
			return true;
		}
	}	
	public function withFilter($filterType, $errorMsg = NULL, $regex = NULL)
	{
         if (!is_null($errorMsg))
        {
            $this->errorMsgFilter = $errorMsg;
        }

		// used to make it easy to later check which one is in place
		$this->filterType = $filterType;
		
		switch($filterType)
		{
			case N::ALLOW_INT:
				$this->filter = new NCore_Port_Filter_Integer($regex);
				break;
			case N::ALLOW_ALPHANUM:
				$this->filter = new NCore_Port_Filter_Alphanum($regex);
				break;
			case N::ALLOW_DOUBLE:
				$this->filter = new NCore_Port_Filter_Double($regex);
				break;
			case N::ALLOW_EMAIL:
				$this->filter = new NCore_Port_Filter_Email($regex);
				break;
			case N::ALLOW_URL:
				$this->filter = new NCore_Port_Filter_URL($regex);
				break;
			case N::ALLOW_US_PHONE:
				$this->filter = new NCore_Port_Filter_USPhone($regex);
				break;
			case N::ALLOW_US_ZIP:
				$this->filter = new NCore_Port_Filter_USZip($regex);
				break;
			case N::ALLOW_TEXT:
				$this->filter = new NCore_Port_Filter_Text($regex);
				break;
            case N::ALLOW_MULTILINE_TEXT:
                $this->filter = new NCore_Port_Filter_MultilineText($regex);
                break;
			case N::ALLOW_XML:
				$this->filter = new NCore_Port_Filter_XML($regex);
				break;
			case N::ALLOW_ALL:
				$this->filter = new NCore_Port_Filter_All($regex);
				break;
			default:
				throw new Exception('The port type '.$filterType.' is not a valid option.');
		}
		
		return $this;
	}
	public function getFilter()
	{
		return $this->filter;
	}
	public function toggleValueHasBeenSet()
	{
		$this->valueHasBeenSet = true;
	}
	public function withValue($value)
	{
		if(!$this->valueHasBeenSet)
		{
			$this->valueHasBeenSet = true;
			$this->value = $value;
		}
		else
		{
			throw new Exception('The value has already been set for the port \''.$this->name.'\' and cannot be reset.');
		}
		
		return $this;
	}
	public function getValue()
	{
		if(is_null($this->valid)) throw new Exception('You have to validate the port \''.$this->getName().'\' before you can call the getValue() method.  The recommended method of validating ports is to 1) add them to a portgroup and then 2) make that portgroup required for a particular pipe by calling withRequiredPortGroup(), which will automatically validate the ports when the pipe is processed.  Or, you can manually validate a port by calling $this->getPort(\'portname\')->validate().');
		
		if($this->valid)
		{
			if(!is_null($this->formatter))
			{
				// make sure to transform ports that are arrays properly
				if(is_array($this->value))
				{
					$tempArray = array();
				
					foreach($this->value as $value)
					{
						$tempArray[] = $this->formatter->format($value);
					}
					
					return $tempArray;
				}
				else
				{
					return $this->formatter->format($this->value);
				}
			}
			else
			{
				return $this->value;
			}
		}
		else
		{
			throw new Exception('The port \''.$this->getName().'\' is not valid, so you can not call the the getValue() method.');
		}
	}
	public function getSafeValue()
	{
		// make sure to transform ports that are arrays properly
		if(is_array($this->getValue()))
		{
			$tempArray = array();
		
			foreach($this->getValue() as $value)
			{
				$tempArray[] = htmlspecialchars($value, ENT_QUOTES);;
			}
			
			return $tempArray;
		}
		else
		{
			return htmlspecialchars($this->getValue(), ENT_QUOTES);
		}
	}
    public function isPresent()
    {
        // chardata ports should always be set, but GPCS subclass will vary
        return true;
    }
	public function isValid()
	{
		// more semantically appropriate wrapper		
		return $this->validate();
	}
	public function withErrorMsg($msg)
	{
		$this->errorMsg = $msg;
		return $this;
	}
	public function getErrorMsg()
	{
		return $this->errorMsg;
	}
	public function withWhitelist($values, $errorMsg = NULL)
	{
        if (!is_null($errorMsg))
        {
            $this->errorMsgWhitelist = $errorMsg;
        }

		if(!is_array($values))
		{
			throw new Exception('The whitelist property for the port \''.$this->getName().'\' must be an array.');
		}	
		
		$this->whitelist = $values;
		return $this;
	}
	public function getWhitelist()
	{
		return $this->whitelist;
	}
	public function withMinLength($value, $errorMsg = NULL)
	{
        if (!is_null($errorMsg))
        {
            $this->errorMsgMinLength = $errorMsg;
        }

		if(!is_int($value))
		{
			throw new Exception('The minimum length for the port \''.$this->getName().'\' must be an integer.');
		}
		
		$this->minLength = $value;
		return $this;
	}
	public function getMinLength()
	{
		return $this->minLength;
	}
	public function withMaxLength($value, $errorMsg = NULL)
	{
        if (!is_null($errorMsg))
        {
            $this->errorMsgMaxLength = $errorMsg;
        }

		if(!is_int($value))
		{
			throw new Exception('The maximum length for the port \''.$this->getName().'\' must be an integer.');
		}

		$this->maxLength = $value;	
		return $this;
	}
	public function getMaxLength()
	{
		return $this->maxLength;
	}
	public function withAllowNull($bool, $errorMsg = NULL)
	{
        if (!is_null($errorMsg))
        {
            $this->errorMsgAllowNull = $errorMsg;
        }    
    
		$this->allowNull = $bool;
		return $this;
	}
	public function getAllowNull()
	{
		return $this->allowNull;
	}
	public function withEncryption($bool)
	{
		$this->encryption = $bool;
		return $this;
	}
	public function getEncryption(){return $this->encryption;}
	public function __call($method, $args)
	{
		throw new Exception('There is no method called \''.$method.'\'() on the port \''.$this->getName().'\'.');
	}
    public function getErrorMsgWhitelist()
    {
        if (!is_null($this->errorMsgWhitelist)) return $this->errorMsgWhitelist;

		$msg = '\''.$this->getFormattedName().'\' ';

		if(count($this->whitelist) > 0)
		{
			$msg.= 'only accepts the following values: '.implode(', ', $this->whitelist).'.';
			
            return $msg;
		}
    }
    public function getErrorMsgMinLength()
    {
        if (!is_null($this->errorMsgMinLength)) return $this->errorMsgMinLength;

		$msg = '\''.$this->getFormattedName().'\' ';

		if($this->getMinLength() == 1)
		{
			$msg.= 'is a required field.';
		}
		else
		{
			$msg.= 'has a minimum length of '.$this->getMinLength().'.';
		}

        return $msg;
    }
    public function getErrorMsgMaxLength()
    {
        if (!is_null($this->errorMsgMaxLength)) return $this->errorMsgMaxLength;

		$msg = '\''.$this->getFormattedName().'\' ';

		$msg.= 'has a maximum length of '.$this->getMaxLength().'.';

        return $msg;
    }
    public function getErrorMsgFilter()
    {
        if (!is_null($this->errorMsgFilter)) return $this->errorMsgFilter;

		$msg = '\''.$this->getFormattedName().'\' only accepts ';

		switch($this->filterType)
		{
			case N::ALLOW_INT:
				$msg.='numbers.';
				break;
			case N::ALLOW_ALPHANUM:
				$msg.='alphanumeric text.';
				break;
			case N::ALLOW_DOUBLE:
				$msg.='numbers optionally containing decimal points.';
				break;
			case N::ALLOW_EMAIL:
				$msg.='valid email addresses.';
				break;
			case N::ALLOW_URL:
				$msg.='valid URL\'s.';
                break;
            case N::ALLOW_US_PHONE:
                $msg .= 'valid US phone numbers.';
				break;
            case N::ALLOW_US_ZIP:
                $msg .= 'valid US zip codes.';
				break;
			case N::ALLOW_XML:
				$msg.='general text.';
				break;
			case N::ALLOW_TEXT:
				$msg.='a single line of text that doesn\'t contain HTML encodings or the characters \'>\' and \'<\'.';
				break;
            case N::ALLOW_MULTILINE_TEXT:
 				$msg.='multi-line text that doesn\'t contain HTML encodings or the characters \'>\' and \'<\'.';
				break;
			case N::ALLOW_ALL:
				$msg.='general text.';
				break;
		}

        return $msg;
    }
    public function getErrorMsgAllowNull()
    {
        if (!is_null($this->errorMsgAllowNull)) return $this->errorMsgAllowNull;

		$msg = '\''.$this->getFormattedName().'\' ';

		$msg.= 'is an expected value, but we did not receive it.';

        return $msg;
    }
    public function getErrorMsgAllowArray()
    {
        if (!is_null($this->errorMsgAllowArray)) return $this->errorMsgAllowArray;

		$msg = '\''.$this->getFormattedName().'\' ';

		$msg.= 'does not allow arrays of data.';

        return $msg;
    }
	public function getAllowArray()
	{
		return $this->allowArray;
	}
	public function withAllowArray($bool, $errorMsg = NULL)
	{
        if (!is_null($errorMsg))
        {
            $this->errorMsgAllowArray = $errorMsg;
        }

		$this->allowArray = $bool;
		return $this;
	}
    // deprecated not that there will only be one formatter per port
	public function addFormatter(NCore_IFormatter $obj)
	{
		$this->formatter = $obj;
		return $this;
	}
    public function withFormatter(NCore_IFormatter $obj)
    {
        $this->formatter = $obj;
        return $this;
    }
	public function getFormatter()
	{
		return $this->formatter;
	}
	protected function initializeValue()
	{
		// hook for the subclasses to set the value
	}
    function __toString()
    {
        return $this->getValue();
    }
}

?>